package com.pingan.haofang.searchcloud.api.query.parser.visitor;

import com.pingan.haofang.searchcloud.api.matedata.IndexMeta;
import com.pingan.haofang.searchcloud.api.query.parser.constant.SqlVisitorStep;
import com.pingan.haofang.searchcloud.api.query.parser.context.SqlExecutionContext;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.GroupBySupport;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.OrderBySupport;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.SqlNode;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.SqlNodeType;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.ColumnNode;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.TextNode;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.aggregate.AggregateFunction;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.aggregate.Count;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.aggregate.Distinct;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.function.Function;
import com.pingan.haofang.searchcloud.api.query.parser.element.QueryBlock;
import com.pingan.haofang.searchcloud.api.query.parser.exception.SqlParseException;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.AllColumns;
import net.sf.jsqlparser.statement.select.AllTableColumns;
import net.sf.jsqlparser.statement.select.FromItemVisitor;
import net.sf.jsqlparser.statement.select.LateralSubSelect;
import net.sf.jsqlparser.statement.select.OrderByElement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.statement.select.SelectItemVisitor;
import net.sf.jsqlparser.statement.select.SelectVisitor;
import net.sf.jsqlparser.statement.select.SetOperationList;
import net.sf.jsqlparser.statement.select.SubJoin;
import net.sf.jsqlparser.statement.select.SubSelect;
import net.sf.jsqlparser.statement.select.ValuesList;
import net.sf.jsqlparser.statement.select.WithItem;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * select语句visitor
 *
 * @author LUYI374
 * @date 2017年5月12日
 * @since 1.0.0
 */
public class SelectStatementVisitor implements SelectVisitor, FromItemVisitor, SelectItemVisitor {

    /**
     * 当前查询块
     */
    private QueryBlock current;

    /**
     * SQL执行上下文
     */
    private SqlExecutionContext context;

    /**
     * 抓取节点索引
     */
    private int selectNodeIndex;

    /**
     * META
     */
    private IndexMeta meta;

    /**
     * 单个查询项
     */
    private boolean singleSelectItem;

    /**
     * 语句级DISTINCT
     */
    private boolean allDistinct;

    /**
     * 字段级DISTINCT
     */
    private boolean columnDistinct;

    /**
     * DISTINCT转换成group列表
     */
    private List<ColumnNode> distinctGroupClause;

    public QueryBlock getQueryBlock() {
        return current;
    }

    @Override
    public void visit(PlainSelect plainSelect) {
        current = new QueryBlock();
        distinctGroupClause = new ArrayList<>();

        // DISTINCT
        if (plainSelect.getDistinct() != null) {
            allDistinct = true;
        }

        // from
        if (plainSelect.getFromItem() != null) {
            plainSelect.getFromItem().accept(this);
        }

        // 抓取字段
        if (plainSelect.getSelectItems() != null) {
            singleSelectItem = plainSelect.getSelectItems().size() == 1;
            for (SelectItem item : plainSelect.getSelectItems()) {
                item.accept(this);
            }
        }

        // 关联查询
        if (plainSelect.getJoins() != null) {
            throw new SqlParseException("join query is not support");
        }

        // where条件
        if (plainSelect.getWhere() != null) {
            ConditionVisitor visitor = new ConditionVisitor(SqlVisitorStep.WHERE, current, context, meta);
            plainSelect.getWhere().accept(visitor);
            current.setWhereConditionGroupList(visitor.getWhereConditions());
            current.setOrIndexSet(visitor.getOrIndexSet());
            visitor.endVisit();
            //加一层过滤，字段如果没有被indexed,就不能作条件查询,即使查询solr也会报错
//            visitor.getWhereConditions().stream().forEach(conditionGroup -> {
//                conditionGroup.getConditionList().stream().forEach(condition -> {
//                    if (condition.getLeft().getMeta().isNotIndexed()) {
//                        throw new SqlParseException("column<" + condition.getLeft().getColumnName() + "> is not indexed,can not in sql-where expression ");
//                    }
//                });
//            });
        }

        // limit语句
        if (plainSelect.getLimit() != null) {
            if (plainSelect.getOffset() != null) {
                current.setOffset(new TextNode(plainSelect.getOffset().getOffset()));
            } else {
                current.setOffset(new TextNode(plainSelect.getLimit().getOffset()));
            }
            current.setLimit(new TextNode(plainSelect.getLimit().getRowCount()));
        }

        // order by语句
        if (plainSelect.getOrderByElements() != null) {
            int len = plainSelect.getOrderByElements().size();
            for (int i = 0; i < len; i++) {
                OrderByElement ele = plainSelect.getOrderByElements().get(i);
                ConditionNodeVisitor visitor =
                        new ConditionNodeVisitor(SqlVisitorStep.ORDERBY, meta, current.getSelectNodeMap());
                ele.getExpression().accept(visitor);

                SqlNode orderByNode = visitor.getConditionNode();

                if (!(orderByNode instanceof OrderBySupport)) {
                    throw new SqlParseException("orderby can only support " + OrderBySupport.SUPPORT_LIST);
                }

                ((OrderBySupport) orderByNode).setAsc(ele.isAsc());
                current.addOrderByNode(orderByNode);
            }
        }

        // group by语句
        if (plainSelect.getGroupByColumnReferences() != null) {
            if (columnDistinct || allDistinct) {
                throw new SqlParseException("distinct query cannot support group by clause");
            }
            int len = plainSelect.getGroupByColumnReferences().size();
            List<SqlNode> groupByClause = new ArrayList<SqlNode>(len);
            TreeMap<Integer, SqlNode> sortedGroupByClause = new TreeMap<Integer, SqlNode>();

            Map<String, Integer> orderByColumnScoreMap = new HashMap<String, Integer>();
            int score = 0;
            for (String columnName : current.getOrderByColumnMap().keySet()) {
                orderByColumnScoreMap.put(columnName, score++);
            }

            /**
             * 修正排序使用，主要针对SOLR<br>
             * 如select B,A,count(*) from T group by A,B;需要修正成select A,B,count(*) from T group by A,B
             */
            int unOrderedScore = 1000000;
            for (Expression expr : plainSelect.getGroupByColumnReferences()) {
                ConditionNodeVisitor visitor = new ConditionNodeVisitor(SqlVisitorStep.GROUP, meta);
                expr.accept(visitor);

                SqlNode node = visitor.getConditionNode();

                if (!(node instanceof GroupBySupport)) {
                    throw new SqlParseException("groupby can only support " + GroupBySupport.SUPPORT_LIST);
                }

                groupByClause.add(node);

                // 独占
                boolean exclusive = ((GroupBySupport) node).exclusive();
                current.setExclusiveGroupBy(exclusive | current.isExclusiveGroupBy());

                if (exclusive && plainSelect.getGroupByColumnReferences().size() > 1) {
                    throw new SqlParseException(node.toString() + " is exclusive in group by expression");
                }

                ColumnNode groupByColumn = ((GroupBySupport) node).getColumn();

                if (orderByColumnScoreMap.containsKey(groupByColumn)) {
                    sortedGroupByClause.put(orderByColumnScoreMap.get(groupByColumn), node);
                } else {
                    sortedGroupByClause.put(unOrderedScore++, node);
                }
            }

            current.setGroupByClause(groupByClause);
            current.setSortedGroupByClause(sortedGroupByClause);
        }

        if (plainSelect.getHaving() != null) {
            throw new SqlParseException("having query cannot be supported");
        }

        current.setUseDistinct(allDistinct || columnDistinct);
        if (current.useDistinct()) {
            current.setGroupByClause(distinctGroupClause);

            // group by排序
            int unOrderedScore = 10000;
            TreeMap<Integer, SqlNode> sortedGroupByClause = new TreeMap<Integer, SqlNode>();
            current.setSortedGroupByClause(sortedGroupByClause);

            Map<String, Integer> orderByColumnScoreMap = new HashMap<String, Integer>();
            int score = 0;
            for (String columnName : current.getOrderByColumnMap().keySet()) {
                orderByColumnScoreMap.put(columnName, score++);
            }

            for (ColumnNode columnNode : distinctGroupClause) {
                if (orderByColumnScoreMap.containsKey(columnNode.getColumnName())) {
                    sortedGroupByClause.put(orderByColumnScoreMap.get(columnNode.getColumnName()), columnNode);
                } else {
                    sortedGroupByClause.put(unOrderedScore++, columnNode);
                }
            }
        }

        if (current.useAggregate()) {
            Set<SqlNode> groupNodeMap = null;
            if (CollectionUtils.isNotEmpty(current.getGroupByClause())) {
                groupNodeMap = new HashSet<>(current.getGroupByClause().size());
                for (SqlNode node : current.getGroupByClause()) {
                    groupNodeMap.add(((GroupBySupport) node).getColumn());
                }
            } else {
                groupNodeMap = new HashSet<>(0);
            }
            for (ColumnNode node : current.getSelectColumnMap().values()) {
                if (!groupNodeMap.contains(node)) {
                    throw new SqlParseException(
                            "groupby columns must include all select columns when using aggregate sql query");
                }
            }

            if (current.getGroupByClause() != null && current.getGroupByClause().size() > 1 && current.usePage()) {
                throw new SqlParseException("agg sql query can only support one group by column when using paginate");
            }
        }
    }

    @Override
    public void visit(Table table) {
        if (StringUtils.isNotBlank(current.getTableName())) {
            throw new SqlParseException("searchcloud can only support single table query");
        }

        String aliasVal = table.getName();
        Alias alias = table.getAlias();
        if (alias != null) {
            aliasVal = alias.getName();
            current.setTableAlias(aliasVal);
        }
        current.setTableName(table.getName());

        IndexMeta meta = context.getMetadataHolder().findIndexMeta(current.getTableName());
        if (meta == null) {
            throw new SqlParseException("can not found available index for indexName: " + current.getTableName());
        }
        this.meta = meta;
    }

    @Override
    public void visit(SubSelect subSelect) {
        throw new SqlParseException("query [" + subSelect.toString() + "] is not support");
    }

    @Override
    public void visit(SubJoin subjoin) {
        throw new SqlParseException("query [" + subjoin.toString() + "] is not support");
    }

    @Override
    public void visit(SetOperationList list) {
        throw new SqlParseException("query [" + list.toString() + "] is not support");
    }

    @Override
    public void visit(LateralSubSelect lateralSubSelect) {
        throw new SqlParseException("query [" + lateralSubSelect.toString() + "] is not support");
    }

    @Override
    public void visit(AllColumns allColumns) {
        if (singleSelectItem) {
            current.setWildcard(true);
        }
        for (String fieldName : meta.getFiledMetas().keySet()) {
            ColumnNode selectNode = new ColumnNode();
            selectNode.setSchema(current.getTableAlias());
            selectNode.setColumnName(fieldName);
            selectNode.setMeta(meta.getFiledMetas().get(fieldName));
            current.addSelectItem(selectNode);
            current.getSelectColumnMap().put(fieldName, selectNode);

            // 目前SOLR SCHEMA需要强依赖主键，如若发现distinct匹配*，则忽略distinct
            if (allDistinct) {
                allDistinct = false;
            }
        }
    }

    @Override
    public void visit(AllTableColumns allTableColumns) {
        if (singleSelectItem) {
            current.setWildcard(true);
        }
        for (String fieldName : meta.getFiledMetas().keySet()) {
            ColumnNode selectNode = new ColumnNode();
            selectNode.setSchema(current.getTableAlias());
            selectNode.setColumnName(fieldName);
            selectNode.setMeta(meta.getFiledMetas().get(fieldName));
            current.addSelectItem(selectNode);
            current.getSelectColumnMap().put(fieldName, selectNode);

            // 目前SOLR SCHEMA需要强依赖主键，如若发现distinct匹配*，则忽略distinct
            if (allDistinct) {
                allDistinct = false;
            }
        }
    }

    /**
     * select抓取字段解析
     */
    @Override
    public void visit(SelectExpressionItem item) {
        SelectNodeVisitor visitor = new SelectNodeVisitor(meta, allDistinct);
        item.getExpression().accept(visitor);
        SqlNode selectNode = visitor.getSelectNode();
        if (selectNode != null) {
            if (item.getAlias() != null) {
                selectNode.setAlias(item.getAlias().getName());
            }
            current.addSelectItem(selectNode);
            // 分组查询函数
            if (selectNode.getType() == SqlNodeType.AGGREGATE_FUNCTION) {

                if (allDistinct) {
                    throw new SqlParseException("distinct query cannot support aggregate function");
                }

                // 针对COUNT的特殊处理
                if (selectNode.getClass() == Count.class && CollectionUtils.isEmpty(selectNode.getColumns())) {
                    ColumnNode node = new ColumnNode();
                    node.setColumnName(meta.getPk().getName());
                    ((Count) selectNode).setParams(Arrays.asList(node));
                    ((Count) selectNode).setWildcard(true);
                    current.addAggregateClause(selectNodeIndex, (AggregateFunction) selectNode);
                } else if (selectNode.getClass() == Distinct.class) {
                    columnDistinct = true;
                    ColumnNode columnNode = ((Distinct) selectNode).getColumn();
                    if (meta.getPk().getName().equals(columnNode.getColumnName())) {
                        columnDistinct = false;
                    } else {
                        distinctGroupClause.add(columnNode);
                    }
                } else if (CollectionUtils.isEmpty(selectNode.getColumns())) {
                    throw new SqlParseException("aggregate function must contain at least one param");
                } else {
                    current.addAggregateClause(selectNodeIndex, (AggregateFunction) selectNode);
                }

            } else if (selectNode.getType() == SqlNodeType.FUNCTION) {
                current.addFunctionClause(selectNodeIndex, (Function) selectNode);
                if (allDistinct) {
                    throw new SqlParseException("distinct query cannot support function");
                }
            } else if (selectNode.getType() == SqlNodeType.COLUMN) {
                ColumnNode columnNode = (ColumnNode) selectNode;
                current.getSelectColumnMap().put(columnNode.getColumnName(), columnNode);

                // 针对distinct语句的特殊处理
                if (allDistinct) {
                    distinctGroupClause.add(columnNode);
                }
            } else {
                throw new SqlParseException("select clause can only support column or function");
            }
        }
        selectNodeIndex++;
    }

    @Override
    public void visit(WithItem withItem) {
        throw new SqlParseException("query [" + withItem.toString() + "] is not support");
    }

    @Override
    public void visit(ValuesList valuesList) {
        throw new SqlParseException("query [" + valuesList.toString() + "] is not support");
    }

    public void setContext(SqlExecutionContext context) {
        this.context = context;
    }

    public IndexMeta getMeta() {
        return meta;
    }

}
